/*
 * Copyright (C) 2011  Romain DUBOIS
 * 
 * This file is part of NeMoS - Network and Modular Software
 * 
 * NeMoS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * NeMoS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with NeMoS.  If not, see <http://www.gnu.org/licenses/>.
 */
package nemos.util.log.output;

import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import java.util.logging.XMLFormatter;

import nemos.util.PropertyHelper;

import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.ConfigurationPolicy;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceStrategy;
import org.osgi.framework.Constants;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.log.LogEntry;
import org.osgi.service.log.LogListener;
import org.osgi.service.log.LogReaderService;
import org.osgi.service.log.LogService;

/**
 * JulListener : The JUL log listener.
 * 
 * <p>
 * This class provides a simple way to write log message into a file or to the
 * console, without adding external library. Loggers should be configured
 * through the {@link ConfigurationAdmin} service : no default logger is
 * configured.
 * </p>
 * 
 * @author Romain DUBOIS
 */
@Component(specVersion = "1.1", name = "nemos.util.log.jul", policy = ConfigurationPolicy.REQUIRE)
@Reference(name = JulListener.LOG_READER, referenceInterface = LogReaderService.class, strategy = ReferenceStrategy.LOOKUP)
public class JulListener implements LogListener {

    /** The log reader service reference name */
    static final String LOG_READER = "logReader"; //$NON-NLS-1$

    /** The platform end of line */
    private static final String EOL_PROP = "line.separator"; //$NON-NLS-1$
    /** The XML file extension */
    private static final String XML_EXTENSION = ".xml"; //$NON-NLS-1$
    /** The logger name property name */
    private static final String LOGGER_NAME = "nemos.util.log.jul.name"; //$NON-NLS-1$
    /** The logger level property name */
    private static final String LOGGER_LEVEL = "nemos.util.log.jul.level"; //$NON-NLS-1$
    /** The logger file name property name */
    private static final String LOGGER_FILE = "nemos.util.log.jul.file"; //$NON-NLS-1$
    /** The logger format property name */
    private static final String LOGGER_FORMAT = "nemos.util.log.jul.format"; //$NON-NLS-1$

    // Remove console handler from root logger
    {
        final Logger l = Logger.getLogger(""); //$NON-NLS-1$
        for (final Handler lHandler : l.getHandlers()) {
            l.removeHandler(lHandler);
        }
    }

    /** The current logger */
    private Logger mLogger;

    /**
     * Constructor
     */
    public JulListener() {
        super();
    }

    /**
     * Start the logger.
     * 
     * @param pContext
     *            The component context
     * @param pProperties
     *            The component properties
     * @throws IOException
     *             If outputis invalid or can not be used
     */
    @Activate
    public void start(final ComponentContext pContext, final Map<String, Object> pProperties)
            throws IOException {

        // Create logger
        final String lName = (String) pProperties.get(LOGGER_NAME);
        if (lName == null) {
            mLogger = Logger.getAnonymousLogger();
        } else {
            mLogger = Logger.getLogger(lName);
        }

        // Configure
        final int lLevel = PropertyHelper.getInt(pProperties.get(LOGGER_LEVEL),
                Integer.valueOf(LogService.LOG_INFO)).intValue();
        mLogger.setLevel(getJulLevel(lLevel));
        final String lFileName = (String) pProperties.get(LOGGER_FILE);
        final Handler lHandler;
        if (lFileName == null) {
            lHandler = new ConsoleHandler();
        } else {
            final File lLogDir = new File(lFileName).getParentFile();
            if (!lLogDir.exists()) {
                lLogDir.mkdirs();
            }
            lHandler = new FileHandler(lFileName, true);
        }
        final Formatter lFormatter;
        final String lFormat = (String) pProperties.get(LOGGER_FORMAT);
        if (lFileName != null && lFileName.endsWith(XML_EXTENSION)) {
            lFormatter = new XMLFormatter();
        } else if (lFormat == null) {
            lFormatter = new SimpleFormatter();
        } else {
            lFormatter = new CustomizedFormatter(lFormat, pContext.getBundleContext().getProperty(
                    EOL_PROP));
        }
        lHandler.setFormatter(lFormatter);
        mLogger.addHandler(lHandler);
        ((LogReaderService) pContext.locateService(LOG_READER)).addLogListener(this);
    }

    /**
     * Stop the logger.
     * 
     * @param pContext
     *            The context
     */
    @Deactivate
    public void stop(final ComponentContext pContext) {
        ((LogReaderService) pContext.locateService(LOG_READER)).removeLogListener(this);
    }

    /**
     * Convert an OSGi log level to a JUL Level.
     * 
     * @param pLogLevel
     *            The OSGi log level
     * @return The matching JUL Level
     */
    protected Level getJulLevel(final int pLogLevel) {
        Level lLevel;
        switch (pLogLevel) {
            case LogService.LOG_DEBUG:
                lLevel = Level.FINE;
                break;

            case LogService.LOG_INFO:
                lLevel = Level.INFO;
                break;

            case LogService.LOG_WARNING:
                lLevel = Level.WARNING;
                break;

            case LogService.LOG_ERROR:
                lLevel = Level.SEVERE;
                break;

            default:
                if (pLogLevel > 0) {
                    lLevel = Level.ALL;
                } else {
                    lLevel = Level.OFF;
                }
        }
        return lLevel;
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.osgi.service.log.LogListener#logged(org.osgi.service.log.LogEntry)
     */
    @Override
    public void logged(final LogEntry pEntry) {
        mLogger.log(getRecord(pEntry));
    }

    /**
     * Convert the OSGi log entry into a JUL log record.
     * 
     * @param pEntry
     *            The OSGi entry
     * @return The matching record
     */
    protected LogRecord getRecord(final LogEntry pEntry) {
        final LogRecord lRecord = new LogRecord(getJulLevel(pEntry.getLevel()), pEntry.getMessage());
        lRecord.setLoggerName(mLogger.getName());
        lRecord.setMillis(pEntry.getTime());
        lRecord.setThrown(pEntry.getException());
        if (pEntry.getBundle() != null) {
            lRecord.setSourceClassName(pEntry.getBundle().getSymbolicName());
        }
        if (pEntry.getServiceReference() != null) {
            lRecord.setSourceMethodName((String) pEntry.getServiceReference().getProperty(
                    Constants.SERVICE_PID));
        }
        return lRecord;
    }
}
